package com.lou1052.redis.jedis.ext.client.single;

import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.commons.pool2.impl.GenericObjectPoolExt;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.util.IPool;
import redis.clients.util.JedisURIHelper;
import redis.clients.util.Pool;

import java.net.URI;
import java.util.Deque;
import java.util.function.Supplier;

/**
 * Created with IntelliJ IDEA.
 * User: liuqing
 * Date: 2015/10/25
 * Time: 22:22
 * To change this template use File | Settings | File Templates.
 */
public class JedisExtPool extends Pool<JedisExt> implements IPool<JedisExt> {
    protected GenericObjectPoolExt<JedisExt> internalPool;
    public JedisExtPool() {
        this(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT,null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host) {
        this(poolConfig, host, Protocol.DEFAULT_PORT, Protocol.DEFAULT_TIMEOUT, null,
                Protocol.DEFAULT_DATABASE, null,null);
    }

    public JedisExtPool(String host, int port) {
        this(new GenericObjectPoolConfig(), host, port, Protocol.DEFAULT_TIMEOUT, null,
                Protocol.DEFAULT_DATABASE, null,null);
    }

    public JedisExtPool(final URI uri) {
        this(new GenericObjectPoolConfig(), uri, Protocol.DEFAULT_TIMEOUT,null);
    }

    public JedisExtPool(final URI uri, final int timeout) {
        this(new GenericObjectPoolConfig(), uri, timeout,null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
                        int timeout, final String password) {
        this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE, null,null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, final int port) {
        this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, null, Protocol.DEFAULT_DATABASE, null, null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, final int port,
                        final int timeout) {
        this(poolConfig, host, port, timeout, null, Protocol.DEFAULT_DATABASE, null, null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
                        int timeout, final String password, final int database) {
        this(poolConfig, host, port, timeout, password, database, null, null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
                        int timeout, final String password, final int database, final String clientName) {
        this(poolConfig, host, port, timeout, timeout, password, database, clientName, null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
                        final int connectionTimeout, final int soTimeout, final String password, final int database,
                        final String clientName) {
        this(poolConfig, new JedisExtFactory(host, port, connectionTimeout, soTimeout, password,
                database, clientName), null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final URI uri) {
        this(poolConfig, uri, Protocol.DEFAULT_TIMEOUT,null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final URI uri, final int timeout) {
        this(poolConfig, uri, timeout, timeout,null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final URI uri,
                        final int connectionTimeout, final int soTimeout) {
        this(poolConfig, new JedisExtFactory(uri, connectionTimeout, soTimeout, null), null);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final JedisExtFactory jedisExFactory,
                        final Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        initPool(poolConfig, jedisExFactory, dequeSupplier);
    }

    public JedisExtPool(final String host) {
        this(host, null);
    }

    public JedisExtPool(Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT, dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(poolConfig, host, Protocol.DEFAULT_PORT, Protocol.DEFAULT_TIMEOUT, null,
                Protocol.DEFAULT_DATABASE, null,dequeSupplier);
    }

    public JedisExtPool(String host, int port, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(new GenericObjectPoolConfig(), host, port, Protocol.DEFAULT_TIMEOUT, null,
                Protocol.DEFAULT_DATABASE, null, dequeSupplier);
    }

    public JedisExtPool(final String host, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        URI uri = URI.create(host);
        if (JedisURIHelper.isValid(uri)) {
            String h = uri.getHost();
            int port = uri.getPort();
            String password = JedisURIHelper.getPassword(uri);
            int database = JedisURIHelper.getDBIndex(uri);
            this.internalPool = new GenericObjectPoolExt<JedisExt>(new JedisExtFactory(h, port,
                    Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, password, database, null),
                    new GenericObjectPoolConfig(),dequeSupplier);
        } else {
            this.internalPool = new GenericObjectPoolExt<JedisExt>(new JedisExtFactory(host,
                    Protocol.DEFAULT_PORT, Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, null,
                    Protocol.DEFAULT_DATABASE, null), new GenericObjectPoolConfig(),dequeSupplier);
        }
    }

    public JedisExtPool(final URI uri, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(new GenericObjectPoolConfig(), uri, Protocol.DEFAULT_TIMEOUT,dequeSupplier);
    }

    public JedisExtPool(final URI uri, final int timeout, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(new GenericObjectPoolConfig(), uri, timeout,dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
                        int timeout, final String password, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(poolConfig, host, port, timeout, password, Protocol.DEFAULT_DATABASE, null, dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, final int port, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, null, Protocol.DEFAULT_DATABASE, null, dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, final int port,
                        final int timeout, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(poolConfig, host, port, timeout, null, Protocol.DEFAULT_DATABASE, null, dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
                        int timeout, final String password, final int database, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(poolConfig, host, port, timeout, password, database, null, dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
                        int timeout, final String password, final int database, final String clientName, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(poolConfig, host, port, timeout, timeout, password, database, clientName, dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
                        final int connectionTimeout, final int soTimeout, final String password, final int database,
                        final String clientName, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        initPool(poolConfig, new JedisExtFactory(host, port, connectionTimeout, soTimeout, password,
                database, clientName), dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final URI uri, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(poolConfig, uri, Protocol.DEFAULT_TIMEOUT, dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final URI uri, final int timeout, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        this(poolConfig, uri, timeout, timeout, dequeSupplier);
    }

    public JedisExtPool(final GenericObjectPoolConfig poolConfig, final URI uri,
                        final int connectionTimeout, final int soTimeout, Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {
        initPool(poolConfig, new JedisExtFactory(uri, connectionTimeout, soTimeout, null), dequeSupplier);
    }

    @Override
    public JedisExt getResource() {
        JedisExt jedis = super.getResource();
        jedis.setDataSource(this);
        return jedis;
    }

    /**
     * @deprecated starting from Jedis 3.0 this method won't exist. Resouce cleanup should be done
     *             using @see {@link Jedis#close()}
     */
    @Deprecated
    public void returnBrokenResource(final JedisExt resource) {
        if (resource != null) {
            returnBrokenResourceObject(resource);
        }
    }

    /**
     * @deprecated starting from Jedis 3.0 this method won't exist. Resouce cleanup should be done
     *             using @see {@link Jedis#close()}
     */
    @Deprecated
    public void returnResource(final JedisExt resource) {
        if (resource != null) {
            try {
                resource.resetState();
                returnResourceObject(resource);
            } catch (Exception e) {
                returnBrokenResource(resource);
                throw new JedisException("Could not return the resource to the pool", e);
            }
        }
    }


    @Override
    public void close() {
        destroy();
    }

    public boolean isClosed() {
        return this.internalPool.isClosed();
    }


    public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<JedisExt> factory,Supplier<Deque<PooledObject<JedisExt>>> dequeSupplier) {

        if (this.internalPool != null) {
            try {
                closeInternalPool();
            } catch (Exception e) {
            }
        }

        this.internalPool = new GenericObjectPoolExt<>(factory, poolConfig,dequeSupplier);
    }


    /**
     * @deprecated starting from Jedis 3.0 this method won't exist. Resouce cleanup should be done
     *             using @see {@link Jedis#close()}
     */
    @Deprecated
    public void returnResourceObject(final JedisExt resource) {
        if (resource == null) {
            return;
        }
        try {
            internalPool.returnObject(resource);
        } catch (Exception e) {
            throw new JedisException("Could not return the resource to the pool", e);
        }
    }

    public void destroy() {
        closeInternalPool();
    }

    protected void returnBrokenResourceObject(final JedisExt resource) {
        try {
            internalPool.invalidateObject(resource);
        } catch (Exception e) {
            throw new JedisException("Could not return the resource to the pool", e);
        }
    }

    protected void closeInternalPool() {
        try {
            internalPool.close();
        } catch (Exception e) {
            throw new JedisException("Could not destroy the pool", e);
        }
    }

    public int getNumActive() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getNumActive();
    }

    public int getNumIdle() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getNumIdle();
    }

    public int getNumWaiters() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getNumWaiters();
    }

    public long getMeanBorrowWaitTimeMillis() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getMeanBorrowWaitTimeMillis();
    }

    public long getMaxBorrowWaitTimeMillis() {
        if (poolInactive()) {
            return -1;
        }

        return this.internalPool.getMaxBorrowWaitTimeMillis();
    }

    private boolean poolInactive() {
        return this.internalPool == null || this.internalPool.isClosed();
    }

    public void addObjects(int count) {
        try {
            for (int i = 0; i < count ; i++) {
                this.internalPool.addObject();
            }
        } catch (Exception e) {
            throw new JedisException("Error trying to add idle objects", e);
        }
    }
}
